/**
 * snd.js v0.0
 * 
 * CREATIVE COMMONS ATTRIBUTION 4.0
 * (c) N_H <h.10x64@gmail.com>
 * 
 * Prease refer to
 *     http://creativecommons.org/licenses/by/4.0/
 **/
 
 /**
 * @namespace snd.jsの基幹ネームスペースです。
 * @version 62190
 */
snd = {VERSION: "62190", IS_BETA:true};

/*** CONSTANTS ***/

/**
 * @namespace 音源のステータスを表す値を入れるネームスペースです。
 */
snd.status = {};
/**
 * 音源が未設定などの理由で、ステータスがまだ定まっていないことを表す値です。
 */
snd.status.NONE = "none";
/**
 * 音源の読込が終了するなどして、音源の再生が可能な状態になっていることを表す値です。
 */
snd.status.READY = "ready";
/**
 * 音源の再生が開始され、再生中であることを表す値です。
 */
snd.status.STARTED = "started";
/**
 * 音源の再生が中断・終了し、停止したことを表す値です。
 */
snd.status.ENDED = "ended";

/**
 * @namespace 音源の種類をあらわす値を入れるネームスペースです。
 */
snd.srctype = {};
/**
 * 使用される音源の種類が未定であることを表す値です。
 */
snd.srctype.NONE = "none";
/**
 * 使用される音源の種類がAudioBufferNodeであることを表す値です。
 */
snd.srctype.AUDIO_BUFFER = "AudioBuffer";
/**
 * 使用される音源の種類がMediaStreamAudioSourceNodeであることを表す値です。
 */
snd.srctype.MEDIA_STREAM = "MediaStream";
/**
 * 使用される音源の種類がMediaElementAudioSourceNodeであることを表す値です。
 */
snd.srctype.MEDIA_ELEMENT = "MediaElement";
/*** VECTOR CLASS ***/

/**
 * @class 3次元ベクトルクラスです。
 *      球座標としても使われ、その場合、x, y, zの値はそれぞれ
 *      x: 方位角
 *      y: 仰角
 *      z: 距離
 *      として扱われます。
 */
snd.vec3 = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
};

snd.vec3.prototype.add = function(pos) {
    return new snd.vec3(this.x + pos.x, this.y + pos.y, this.z + pos.z);
};

snd.vec3.prototype.mult = function(a) {
    return new snd.vec3(a * this.x, a * this.y, a * this.z);
};

snd.vec3.prototype.sub = function(pos) {
    return this.add(pos.mult(-1));
};

snd.vec3.prototype.length = function() {
    return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
};

snd.vec3.prototype.normalize = function() {
    return this.mult(1.0 / this.length());
};

snd.vec3.prototype.toSphericalCoordinate = function() {
    var azimuth = Math.atan2(this.z, this.x);
    var elevation = Math.atan2(this.y, Math.sqrt(this.z * this.z + this.x * this.x));
    var length = this.length();
    return new snd.vec3(azimuth, elevation, length);
};

snd.vec3.prototype.toOrthogonalCoordinate = function() {
    var retY = this.z * Math.sin(this.y);
    var retX = this.z * Math.cos(this.y) * Math.cos(this.x);
    var retZ = this.z * Math.cos(this.y) * Math.sin(this.x);
    return new snd.vec3(retX, retY, retZ);
};

/**
 * @class 位置と向きをあらわすクラスです。
 *      位置を表すposベクトル、正面向きを表すdirベクトル、上方向を表すupベクトルの3つのベクトルで位置と向きを管理します。
 */
snd.PosDir = function() {
    this.pos = new snd.vec3(0, 0, 0);
    this.dir = new snd.vec3(0, 0, -1);
    this.up = new snd.vec3(0, 1, 0);
};

snd.PosDir.prototype.setPosition = function(x, y, z) {
    this.pos.x = x;
    this.pos.y = y;
    this.pos.z = z;
};

snd.PosDir.prototype.setDir = function(x, y, z) {
    this.dir.x = x;
    this.dir.y = y;
    this.dir.z = z;
    this.dir.normalize();
};

snd.PosDir.prototype.setTop = function(x, y, z) {
    this.up.x = x;
    this.up.y = y;
    this.up.z = z;
    this.up.normalize();
};

snd.PosDir.prototype.setOrientation = function(x, y, z, ux, uy, uz) {
    this.setDir(x, y, z);
    this.setTop(ux, uy, uz);
};

snd.PosDir.prototype.setOrientationBySpherical = function(dir, up) {
    var orthDir;
    if (up != null) {
        var orthUp = up.toOrthogonalCoordinate();
        this.setTop(orthUp.x, orthUp.y, orthUp.z);
        
        var rotDir = dir.sub(up);
        rotDir.normalize();
        
        orthDir = rotDir.toOrthogonalCoordinate();
    } else {
        orthDir = dir.toOrthogonalCoordinate();
    }
    
    this.setDir(orthDir.x, orthDir.y, orthDir.z);
};

/**
 * @class 1つのオーディオユニットを定義するクラスです。
 *      インタフェースクラスなので、継承されることを前提としています。
 *      引数にAudioUnitを要求するメソッドに渡すオブジェクトは、ここで定義されている各メソッドを実装している必要があります。
 */
snd.AudioUnit = function() {
    // IT'S A INTERFACE CLASS
    // PLEASE OVERRIDE ALL METHODS
};

/**
 * 音源をこのユニットに接続します。
 *      渡される音源はAudioNodeクラスを継承するオブジェクトです。
 *      AudioUnitを継承するクラスは、このメソッド内で このユニットで定義される
 *      オーディオノードのチェーンの最初のノードへ引数で渡された音源を接続してください。
 *      AudioUnitの使用法によっては、多数の音源から複数回呼び出される可能性もあります。
 * @param {AudioNode} src 音源となるノードです(AudioBufferSourceNode, MediaElementAudioSourceNode, etc...)
 * @see {snd.GainOnlyUnit} snd.GainOnlyUnit ボリュームのみで構成された最も単純なAudioUnit
 */
snd.AudioUnit.prototype.connectSource = function(src) {
    // PLEASE OVERRIDE ME
};

/**
 * 渡された音源とこのユニットとの接続を切断します。
 *      渡される音源はAudioNodeクラスを継承するオブジェクトです。
 *      AudioUnitを継承するクラスは、このメソッド内で このユニットで定義される
 *      オーディオノードのチェーンの最初のノードと引数で渡された音源との接続を切断してください。
 *      AudioUnitの使用法によっては、多数の音源から複数回呼び出される可能性もあります。
 * @param {AudioNode} src ユニットから切断するAudioNode
 */
snd.AudioUnit.prototype.disconnectSource = function(src) {
    // PLEASE OVERRIDE ME
};

/**
 * 他のAudioNodeやAudioUnitと接続する際に使用されるAudioNodeを取得します。
 *      AudioUnitを継承するクラスは最終的な出力がでるAudioNodeを戻してください。
 * @returns {AudioNode} このユニットが他のAudioNodeやAudioUnitと接続する際に使われるAudioNode
 */
snd.AudioUnit.prototype.getConnector = function() {
    // PLEASE OVERRIDE ME
};

/*** GAIN ONLY UNIT ***/

/**
 * @class 主ボリュームのみの単純なユニットです。
 * @extends snd.AudioUnit
 */
snd.GainOnlyUnit = function() {
    snd.AudioUnit.apply(this, arguments);
    this.gain = snd.AUDIO_CONTEXT.createGain();
};
snd.GainOnlyUnit.prototype = Object.create(snd.AudioUnit.prototype);
snd.GainOnlyUnit.prototype.constructor = snd.GainOnlyUnit;

/**
 * @see snd.AudioUnit#connectSource
 * @param {AudioNode}
 */
snd.GainOnlyUnit.prototype.connectSource = function(src) {
    src.connect(this.gain);
};

/**
 * @see snd.AudioUnit#disconnectSource
 * @param {AudioNode}
 */
snd.GainOnlyUnit.prototype.disconnectSource = function(src) {
    src.disconnect(this.gain);
};

/**
 * @see snd.AudioUnit#getConnector
 */
snd.GainOnlyUnit.prototype.getConnector = function() {
    return this.gain;
};

/**
 * メインボリュームを取得します。
 *      ボリュームの使用法については<a href="http://g200kg.github.io/web-audio-api-ja/#GainNode">web audio api仕様のGainNode</a>を参照してください。
 * @returns {snd.GainNode} this.gain
 */
snd.GainOnlyUnit.prototype.getGain = function() {
    return this.gain;
};

/**
 * @class リスナを表すクラスです。
 *      AudioContext#Listenerをラップしています。
 *      <a href="#setListener">setListener</a>メソッドを呼び出すまではthis.listenerはnullで、実際の出力へは反映されません。
 *      this.listenerがnullである間も位置は記録されるので、<a href="#setListener">setListener</a>メソッドが呼び出された時点で
 *      WebAudioAPIのListenerに、そのオブジェクトで設定された姿勢が反映されます。
 * @param {Listener} listener AudioContext.listener
 */
snd.Listener = function(listener) {
    snd.PosDir.apply(this, arguments);
    this.listener = listener;

    if (listener != null) {
        this.listener.setOrientation(this.dir.x, this.dir.y, this.dir.z, this.up.x, this.up.y, this.up.z);
    }
};
snd.Listener.prototype = Object.create(snd.PosDir.prototype);
snd.Listener.prototype.constructor = snd.Listener;

/**
 * listenerを設定します。
 *      このメソッドで設定されるまで、WebAudioAPIのlistenerには反映されません。
 * @param {Listener} listener
 */
snd.Listener.prototype.setListener = function(listener) {
    this.listener = listener;
    this.setPosition(this.pos.x, this.pos.y, this.pos.z);
    this.setOrientation(this.dir.x, this.dir.y, this.dir.z, this.up.x, this.up.y, this.up.z);
};

/**
 * このオブジェクトに設定されているlistenerをnullへリセットします。
 */
snd.Listener.prototype.resetListener = function() {
    this.setListener(null);
};

/**
 * リスナの位置を設定します。
 * @param {float} x X軸の値
 * @param {float} y Y軸の値
 * @param {float} z Z軸の値
 */
snd.Listener.prototype.setPosition = function(x, y, z) {
    snd.PosDir.prototype.setPosition.call(this, x, y, z);
    if (this.listener != null) {
        this.listener.setPosition(x, y, z);
    }
};

/**
 * リスナの向きを設定します。
 * @param {flaot} x 正面方向ベクトルのX値
 * @param {flaot} y 正面方向ベクトルのY値
 * @param {flaot} z 正面方向ベクトルのZ値
 * @param {flaot} ux 上方向ベクトルのX値
 * @param {flaot} uy 上方向ベクトルのY値
 * @param {flaot} uz 上方向ベクトルのZ値
 */
snd.Listener.prototype.setOrientation = function(x, y, z, ux, uy, uz) {
    snd.PosDir.prototype.setOrientation.call(this, x, y, z, ux, uy, uz);
    if (this.listener != null) {
        this.listener.setOrientation(this.dir.x, this.dir.y, this.dir.z, this.up.x, this.up.y, this.up.z);
    }
};

/**
 * 球座標でリスナーの向きを設定します。
 * @param {snd.vec3} dir dir.x:方位角 dir.y:仰角 dir.z:距離
 * @param {snd.vec3} up up.x:方位角 up.y:仰角 up.z:距離
 */
snd.Listener.prototype.setOrientationBySpherical = function(dir, up) {
    snd.PosDir.prototype.setOrientationBySpherical.call(this, dir, up);
    if (this.listener != null) {
        this.listener.setOrientation(this.dir.x, this.dir.y, this.dir.z, this.up.x, this.up.y, this.up.z);
    }
};

/**
 * 速度を設定します。
 * @param {type} x X軸方向の速度
 * @param {type} y Y軸方向の速度
 * @param {type} z Z軸方向の速度
 */
snd.Listener.prototype.setVelocity = function(x, y, z) {
    if (this.listener != null) {
        this.listener.setVelocity(x, y, z);
    }
};

/**
 * @class BGM等の音源を表すクラスです。
 *      主ボリュームのみを持つ単純なユニットであるGainOnlyUnitを継承したクラスで、
 *      <a href="http://g200kg.github.io/web-audio-api-ja/#AudioBufferSourceNode">WebAudioAPIのAudioBufferSourceNode</a>をラップします。
 * @param {String} id オブジェクトを識別するID
 * @extends {snd.GainOnlyUnit} 
 */
snd.Sound = function(id) {
    snd.GainOnlyUnit.apply(this, arguments);

    // properties
    this.id = id;
    this.status = snd.status.NONE;
    this.type = snd.srctype.NONE;

    this.source = null;
    this.audioBuffer = null;
};
snd.Sound.prototype = Object.create(snd.GainOnlyUnit.prototype);
snd.Sound.prototype.constructor = snd.Sound;

/**
 * この音源の再生を開始します。
 *      引数の内容については、<a href="http://g200kg.github.io/web-audio-api-ja/#AudioBufferSourceNode">Web Audio API仕様のAudioBufferSourceNode</a>を参照してください。
 * @param {Number} when 再生を開始する時刻(秒)
 * @param {Number} offset 再生を開始する位置(秒)
 * @param {Number} duration 再生する長さ(秒)
 */
snd.Sound.prototype.start = function(when, offset, duration) {
    if (this.source != null && this.status == snd.status.READY) {
        if (offset == null) {
            this.source.start(when);
        } else if (duration == null) {
            this.source.start(when, offset);
        } else {
            this.source.start(when, offset, duration);
        }
        this.status = snd.status.STARTED;
    } else {
        if (this.audioBuffer != null && this.type == snd.srctype.AUDIO_BUFFER) {
            this.setAudioBuffer(this.audioBuffer);
            this.start(when, offset, duration);
        }
    }
};

/**
 * 再生を停止します。
 * @param {double} when
 */
snd.Sound.prototype.stop = function(when) {
    if (this.source != null) {
        if (when == null) {
            this.source.stop();
        } else {
            this.source.stop(when);
        }
    }
};

/**
 * この音源がループするかどうかを設定します。
 *      引数の内容については、<a href="http://g200kg.github.io/web-audio-api-ja/#AudioBufferSourceNode">Web Audio API仕様のAudioBufferSourceNode</a>を参照してください。
 * @param {boolean} status ループするか否か
 */
snd.Sound.prototype.setLoop = function(status) {
    if (this.source != null) {
        this.source.loop = status;
    }
};

/**
 * ループの開始位置を設定します。
 * @param {double} when
 */
snd.Sound.prototype.setLoopStart = function(when) {
    if (this.source != null && when != null) {
        this.source.loopStart = when;
    }
};

/**
 * ループの終端を設定します。
 * @param {double} when
 */
snd.Sound.prototype.setLoopEnd = function(when) {
    if (this.source != null && when != null) {
        this.source.loopEnd = when;
    }
};

/**
 * 再生終了時に呼び出されるメソッドです。<br>
 * 内部でstatusアトリビュートにsnd.status.ENDEDを設定しているため、このメソッドを
 * オーバーライドする際は、オーバーライドしたメソッド内でsnd.Sound.prototype.onended.call(this)を使用して
 * 親クラスのonendedを呼び出すようにしてください。
 */
snd.Sound.prototype.onended = function() {
    this.status = snd.status.ENDED;
};

/**
 * この音源のソースとして、AudioBufferSourceNodeを設定します。<br>
 * 読み込んだデータはこのオブジェクトに保持され、次回以降の再生に自動的に再利用されます。<br>
 * また、読込終了時にtypeアトリビュートにsnd.type.AUDIO_BUFFERが設定され、同時にonloadメソッドが呼ばれます。
 * 
 * @param {String} url この音源のソースとなるデータのURL
 */
snd.Sound.prototype.load = function(url) {
    var _this = this;
    var request = new XMLHttpRequest();

    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    request.onload = function() {
        snd.AUDIO_CONTEXT.decodeAudioData(
                request.response,
                function(buf) {
                    _this.setAudioBuffer(buf);
                });
    };

    request.send();
};

/**
 * オーディオバッファを設定するメソッドです。<br>
 * 自動的にAudioContextからAudioBufferSourceNodeを生成し、このオブジェクトのソースとして使用するように設定します。<br>
 * 同時に、typeアトリビュートの値がsnd.srctype.AUDIO_BUFFERに設定されます。
 * 
 * @param {type} audioBuffer
 */
snd.Sound.prototype.setAudioBuffer = function(audioBuffer) {
    this.audioBuffer = audioBuffer;

    var src = snd.AUDIO_CONTEXT.createBufferSource();
    this.source = src;

    this.source.buffer = this.audioBuffer;
    this.connectSource(this.source);
    this.source.onended = this.onended;

    this.type = snd.srctype.AUDIO_BUFFER;
    this.status = snd.status.READY;

    this.onload();
};
 /**
  * @class PannerNodeを使用するパンニングに対応したユニットです。
  * @param {String} id このユニットを表す固有のID
  */
snd.SoundNode = function(id) {
    snd.Sound.apply(this, arguments);
    snd.PosDir.apply(this, arguments);
    
    this.pannerNode = snd.AUDIO_CONTEXT.createPanner();
    this.gain.connect(this.pannerNode);
};
snd.SoundNode.prototype = Object.create(snd.Sound.prototype);
snd.SoundNode.prototype.constructor = snd.SoundNode;

/**
 * @returns {AudioNode}
 * @see snd.AudioUnit#getConnector
 */
 snd.SoundNode.prototype.getConnector = function() {
    return this.pannerNode;
};

/**
 * この音源の位置を設定します。
 * @param {type} x 設定する位置のX値
 * @param {type} y 設定する位置のY値
 * @param {type} z 設定する位置のZ値
 */
 snd.SoundNode.prototype.setPosition = function(x, y, z) {
    snd.PosDir.prototype.setPosition.call(this, x, y, z);
    this.pannerNode.setPosition(x, y, z);
};

/**
 * この音源の向きを設定します
 * @param {type} x 正面方向ベクトルのX値
 * @param {type} y 正面方向ベクトルのY値
 * @param {type} z 正面方向ベクトルのZ値
 */
 snd.SoundNode.prototype.setOrientation = function(x, y, z) {
    snd.PosDir.prototype.setOrientation.call(this, x, y, z);
    this.pannerNode.setOrientation(x, y, z);
};

/**
 * この音源の速度を設定します。
 * @param {type} x 速度ベクトルのX値
 * @param {type} y 速度ベクトルのY値
 * @param {type} z 速度ベクトルのZ値
 */
 snd.SoundNode.prototype.setVelocity = function(x, y, z) {
    this.pannerNode.setVelocity(x, y, z);
};
/**
 * @class シミュレーションする音環境を表すクラスです。<br>
 * リスナーやユニットなどを管理します。<br>
 * さまざまなユニットは最終的にSoundEnvironment#connectAudioUnitメソッドを使って、実際の出力へ反映されるようになります。
 */
snd.SoundEnvironment = function() {
    this.id = 0;
    this.listener = snd.AUDIO_CONTEXT.listener;
    this.listenerList = {};
    this.unitList = {};
    
    this.createListener("DEFAULT");
};

/**
 * 新しいリスナを生成します。<br>
 * リスナーはこのクラスの内部でHashMapとして管理されるため、キー値を引数として渡す必要があります。<br>
 * 複数のリスナーを生成することもできますが、switchListenerメソッドでsnd.LISTENER定数を切り替えるまで実際の出力には影響を及ぼしません。
 * 
 * @param {String} key このリスナーを表すキー値
 * @returns {snd.Listener} 生成されたリスナー
 * @see snd.SoundEnvironment#switchListener
 */
snd.SoundEnvironment.prototype.createListener = function(key) {
    var listener = new snd.Listener();
    this.listenerList[key] = listener;
    return listener;
};

/**
 * リスナーを取得します。
 * @param {String} key
 * @returns {snd.Listener}
 * @see {snd.SoundEnvironment#switchListener}
 */
snd.SoundEnvironment.prototype.getListener = function(key) {
    return this.listenerList[key];
};

/**
 * keyで指定されたリスナーへsnd.LISTENER定数を切り替えます。<br>
 * このメソッドでリスナーを選択しない限り、snd.Listenerへ行った変更は実際の出力へ影響を与えません。<br>
 * snd.LISTENER定数の内容が選択されたリスナーへ変更されるため、(snd.LISTENER定数を使用する限り)多数のリスナーを次々と切り替えたとしても、
 * 常にユニークなリスナを使用することができます。<br>
 * @param {type} key 次の選択リスナーを表すキー値
 */
snd.SoundEnvironment.prototype.switchListener = function(key) {
    for (var k in this.listenerList) {
        this.listenerList[k].resetListener();
    }
    this.listenerList[key].setListener(this.listener);
    
    snd.LISTENER = this.listenerList[key];
};

/**
 * リスナーを削除します。
 * @param {String} key 削除するリスナー
 */
snd.SoundEnvironment.prototype.deleteListener = function(key) {
    if (snd.LISTENER == this.listenerList[key].listener) {
        this.listenerList.resetListener();
    }
    delete this.listenerList[key];
};

/**
 * 新しいユニットを接続します。<br>
 * 各種ユニットは、最終的にこのメソッドを使って実際の出力へ反映されます。
 * @param {type} key 接続するユニットを表すキー値
 * @param {snd.AudioUnit} audioUnit 接続するユニット
 */
snd.SoundEnvironment.prototype.connectAudioUnit = function(key, audioUnit) {
    this.unitList[key] = audioUnit;
    audioUnit.getConnector().connect(snd.AUDIO_CONTEXT.destination);
};

/**
 * 接続済みのユニットを取得します。
 * @param {type} key
 */
snd.SoundEnvironment.prototype.getAudioUnit = function(key) {
    return this.unitList[key];
};

/**
 * 接続されたユニットを切断します。
 * @param {type} key 切断するユニット
 */
snd.SoundEnvironment.prototype.disconnectAudioUnit = function(key) {
    var audioUnit = this.unitList[key];
    audioUnit.getConnector().disconnect(snd.AUDIO_CONTEXT.destination);
    delete this.unitList[key];
};
snd.util = {};

/**
 * 複数ある音源からデータを読み込む場合に使用するためのクラスです。<br>
 * wait関数の代わりに使用してください。<br>
 * コンストラクタに渡されるオブジェクトのonloadメソッドが書き換えられるので注意してください。<br>
 * 
 * @param {snd.Sound} callerList ロードを待機したい音データのリスト
 */
snd.util.DataLoader = function(callerList) {
    var _this = this;
    
    this.callerList = callerList;
    this.loadLogger = [];
    for (var i in this.callerList) {
        var caller = callerList[i];
        caller.onload = function() {
            for (var i in _this.loadLogger) {
                if (_this.loadLogger[i].caller == this) {
                    _this.loadLogger[i].doesLoaded = true;
                    break;
                }
            }
            if (_this.doesAllDataLoaded()) {
                _this.onload();
            }
        };
        this.loadLogger.push({caller:caller, doesLoaded:false});
    }
};

/**
 * このDataLoaderに設定されている全ての音データのロードが完了したかどうかを返します。
 * 全データのロードが終了している場合はtrue、1つでも読込が終了していないデータがある場合はfalseを返します。
 * @returns {Boolean} 全てのデータの読込が終了しているかどうか
 */
snd.util.DataLoader.prototype.doesAllDataLoaded = function() {
    for (var i in this.loadLogger) {
        if (!this.loadLogger[i].doesLoaded) {
            return false;
        }
    }
    return true;
};

/**
 * このDataLoaderに設定されている全ての音データの読込が終了したときに呼び出されるメソッドです。
 * 
 * @returns {undefined}
 */
snd.util.DataLoader.prototype.onload = function() {
    // PLEASE OVERRIDE ME
};

/***  PROPERTIES ***/

/**
 * ブラウザから取得したオーディオコンテキストが入ります。
 *      snd#initメソッドが呼ばれるまで初期化されず、nullとなっている点に注意してください。
 * @type AudioContext
 */
snd.AUDIO_CONTEXT = null;
/**
 * シミュレーション上の聴取環境を管理するクラスのインスタンスが入ります。
 *      リスナーや音源の操作などはこのインスタンスを介して行います。
 * @type snd.SoundEnvironment 
 */
snd.SOUND_ENVIRONMENT = null;
/**
 * 現在選択中のリスナーが入ります。
 *      リスナーは複数用意することが可能ですが、出力へ反映されるリスナは常に選択中の1つのみになっています。
 *      リスナーの追加や選択については、snd.SoundEnvironmentクラスを参照してください。
 * @type type
 * @see snd.SoundEnvironment
 */
snd.LISTENER = null;

/*** METHODS ***/

/**
 * snd.jsを初期化します。
 */
snd.init = function() {
    snd.resetAudioContext();
    snd.SOUND_ENVIRONMENT = new snd.SoundEnvironment();
    snd.SOUND_ENVIRONMENT.switchListener("DEFAULT");
};

/**
 * オーディオコンテキストを初期化します。
 *      snd#initメソッドから呼び出すためのメソッドですので、特別な理由が無い限り使用しないでください。
 * @private
 */
snd.resetAudioContext = function() {
    if (snd.AUDIO_CONTEXT == null) {
        // Create AudioContext
        if (window.AudioContext) {
            // firefox
            snd.AUDIO_CONTEXT = new AudioContext();
        } else if (window.webkitAudioContext) {
            // crome etc
            snd.AUDIO_CONTEXT = new webkitAudioContext();
        }
    }
};
